package com.jbarreto.vc.hibernate;


import static org.hibernate.criterion.Example.create;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import com.jbarreto.vc.dataobjects.misc.MiscUtility;
import com.jbarreto.vc.hibernate.exceptions.InfrastructureException;

/**
 * Utility for Hibernate
 * 
 * @author Jesus Barreto
 */
public class HibernateUtil  {

	private static final Log log = LogFactory.getLog(HibernateUtil.class);
	private static final ThreadLocal<Transaction> threadTransaction = new ThreadLocal<Transaction>();
	private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();
	private static final ThreadLocal threadInterceptor = new ThreadLocal();
	private static Session sessionUtil; 
	protected static final SessionFactory sessionFactory; 
	private static Configuration configuration;

	static {
		try {
            configuration = new Configuration();
			sessionFactory = configuration.configure().buildSessionFactory();
		} catch (Throwable ex) {
			log.error("Building SessionFactory failed.", ex);
			ex.printStackTrace();
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {
		try {
			return sessionFactory;
		} catch (Exception e) {
			log.error("Could not locate hsgoldsf in JNDI", e);
			throw new IllegalStateException(
					"Could not locate hsgoldsf in JNDI");
		}
	}

	/**
	 * Start a new database transaction.
	 */
	public static void beginTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			if (tx == null) {
				log.debug("Starting new database transaction in this thread.");
				tx = getSessionUtil().beginTransaction();
				threadTransaction.set(tx);
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Commit the database transaction.
	 */
	public static void commitTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			if ( tx != null && !tx.wasCommitted()
							&& !tx.wasRolledBack() ) {
				log.debug("Committing database transaction of this thread.");
				tx.commit();
				sessionUtil.flush();
			}
			threadTransaction.set(null);
		} catch (HibernateException ex) {
			rollbackTransaction();
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Rollback the database transaction.
	 */
	public static void rollbackTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			threadTransaction.set(null);
			if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
				log.debug("Tyring to rollback database transaction of this thread.");
				tx.rollback();
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		} finally {
			//closeSession();
		}
	}

	/**
	 * Retrieves the current Session local to the thread.
	 * <p/>
	 * If no Session is open, opens a new Session for the running thread.
	 *
	 * @return Session
	 */
	public static Session getSessionUtil()
		throws InfrastructureException {
		
		Session s = getSessionFactory().openSession();
		System.out.println(String.format("Thread [%d] opens new hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));

		return s;
//		Session s = (Session) threadSession.get();
////		Session s = sessionUtil;
//		try {
//			if (s == null) {
//				log.debug("Opening new Session for this thread.");
//				if (getInterceptor() != null) {
//					log.debug("Using interceptor: " + getInterceptor().getClass());
//					s = getSessionFactory().openSession(getInterceptor());
//				} else {
//					s = getSessionFactory().openSession();
//				}
//				log.debug(String.format("Thread [%d] opens new hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));
//				System.out.println(String.format("Thread [%d] opens new hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));
//				threadSession.set(s);
////				s.clear();
//				sessionUtil = s;
//			}
//		} catch (HibernateException ex) {
//			throw new InfrastructureException(ex);
//		}
//		//s.setCacheMode(CacheMode.REFRESH);
//		log.debug(String.format("Thread [%d] will use hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));
//		System.out.println(String.format("Thread [%d] opens new hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));
//		return s;
	}

	/**
	 * Closes the Session local to the thread.
	 */

	public static void closeSession()
		throws InfrastructureException {
		try {
			Session s = (Session) threadSession.get();
			threadSession.set(null);
//			Session s = sessionUtil;
			
			if (s != null && s.isOpen()) {
				System.out.println(String.format("Thread [%d] close new hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));
				log.debug("Closing Session of this thread.");
				log.debug(String.format("Thread [%d] closes hibernate sessionUtil [%d]", Thread.currentThread().getId(), s.hashCode()));
				s.flush();
				s.clear();
				s.close();
				s = null;
			}else {
				System.out.println(String.format("Thread [%d] trying close hibernate sessionUtil closed", Thread.currentThread().getId()));
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Closes the Session local to the thread.
	 */

	public static void restartSession()
		throws InfrastructureException  {
		try {
			closeSession();
			getSessionUtil();
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	private static Interceptor getInterceptor() {
		Interceptor interceptor =
			(Interceptor) threadInterceptor.get();
		return interceptor;
	}

	public static Serializable save(Object transientInstance) {
		log.debug("save instance");
		 Session session = null;
		 try {
		     session = getSessionUtil();
  			 log.debug("saving object");
		     return session.save(transientInstance);
		} catch (RuntimeException re) {
			log.error("save failed", re);
			throw re;
		}finally{
			if (session != null) {
//				sessionUtil.flush();
//				sessionUtil.clear();
//				sessionUtil.refresh(transientInstance);
			}
		}
	}

	public static void saveOrUpdate(Object transientInstance) {
		log.debug("saveOrUpdate instance");
		 Session session = null;
		 try {
		     session = getSessionUtil();
		     session.saveOrUpdate(transientInstance);
			log.debug("saveOrUpdate successful");
		} catch (RuntimeException re) {
			log.error("saveOrUpdate failed", re);
			throw re;
		}finally{
			if (session != null) {
//				sessionUtil.flush();
//				sessionUtil.clear();
//				sessionUtil.refresh(transientInstance);
			}
		}
	}

	public static void persist(Object transientInstance) throws HibernateException {
		log.debug("persisting instance");
		 Session session = null;
		 try {
		     session = getSessionUtil();
		     session.persist(transientInstance);
			log.debug("persist successful");
		} catch (RuntimeException re) {
			log.error("persist failed", re);
			throw re;
		}finally{
			if (session != null) {
				try {
//					sessionUtil.flush();
//					sessionUtil.clear();
//					sessionUtil.refresh(transientInstance);
				} catch (HibernateException e) {
					throw e;
				}
			}
		}
	}

	public static void attachDirty(Object instance) {
		log.debug("attaching dirty instance");
		try {
			getSessionUtil().saveOrUpdate(instance);
			log.debug("attach successful");
		} catch (RuntimeException re) {
			log.error("attach failed", re);
			throw re;
		}
	}

	public static void attachClean(Object instance) {
		log.debug("attaching clean instance");
		try {
			getSessionUtil().lock(instance, LockMode.NONE);
			log.debug("attach successful");
		} catch (RuntimeException re) {
			log.error("attach failed", re);
			throw re;
		}
	}

	public static <T> T load(int id, Class<T> clazz) {
		log.debug("Loading instance by id: " + id + " , Class: " + clazz);
		try {
			T instance = (T) getSessionUtil().load(clazz, id);
			if (instance == null) {
				log.debug("get successful, no instance found");
			} else {
				log.debug("get successful, instance found");
			}
			return instance;
		} catch (RuntimeException re) {
			log.error("Load object by Id failed", re);
			throw re;
		}
	}

	public static <T>T merge(T detachedInstance) {
		log.debug("merging Presentation instance");
		Session session = getSessionUtil();
		try {
			T result = (T) session.merge(detachedInstance);
			log.debug("merge successful");
			return result;
		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}finally{
			if (session != null) {
//				sessionUtil.flush();
//				sessionUtil.clear();
//				sessionUtil.refresh(detachedInstance);
			}
		}
	}


	public static void update(Object instance) {
		log.debug("updating Object instance");
		Session session = getSessionUtil();
		try {
			session.saveOrUpdate(instance);
			log.debug("updating successful");
		} catch (RuntimeException re) {
			log.error("updating failed", re);
			throw re;
		}finally{
			if (session != null) {
				session.flush();
//				sessionUtil.clear();
//				sessionUtil.refresh(instance);
			}
		}
	}

	public static void delete(Object persistentInstance) {
		log.debug("deleting Object instance");
		if(persistentInstance == null) return; 
		Session session = getSessionUtil();
		try {
			session.delete(persistentInstance);
			log.debug("delete successful");
		} catch (RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}finally{
			if (session != null) {
//				sessionUtil.flush();
			}
		}
	}

	public static <T> T findById(int id, Class<T> clazz) {
		log.debug("getting instance by id: " + id + " , Class: " + clazz);
		Session session = getSessionUtil();
		try {
			T instance = (T) session.get(clazz, id);
			if (instance == null) {
				log.debug("get successful, no instance found");
			} else {
				log.debug("get successful, instance found");
			}
			return instance;
		} catch (RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}finally{
			if (session != null) {
//				sessionUtil.flush();
			}
		}
	}

	public static <T> List<T> findByExample(T instance, Class clazz) {
		log.debug("finding Object instance by example");
		try {
			List<T> results = (List<T>) getSessionUtil()
					.createCriteria(clazz).add(
							create(instance)).list();
			log.debug("find by example successful, result size: "
					+ results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	public static <T> T findObject(T instance, Class clazz) {
		log.debug("finding Object instance by example");
		try {
			T results = (T) getSessionUtil()
					.createCriteria(clazz).add(
							create(instance)).uniqueResult();
			log.debug("find by example successful, result size: "
					+ results);
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}
	
	public int getEntityCount(Class clazz, String filter) {
		List<Class> classList = new ArrayList<Class>();
		classList.add(clazz);
		return getEntityCount(classList,filter);
	}

	public int getEntityCount( List<Class> fromClasses, String filter) {
		log.debug("getEntityCount");
		Session session = null;
		try {
			//String selectClassesNames = listClassNames(selectClasses);
			String fromClassesNames   = listClassNames(fromClasses);
			session = getSessionUtil();
			String SQL_QUERY = String.format("select count(*) from %s %s ", fromClassesNames, filter);

			Query query = session.createQuery(SQL_QUERY);
			
			return ((Long) query.iterate().next()).intValue();
		} catch (RuntimeException re) {
			log.error("getEntityCount", re);
			throw re;
		} finally {
			if (session != null) {
//				sessionUtil.flush();
			}
		}
	}

	private String listClassNames(List<Class> classes) {
		String classesNames = "";
		Iterator<Class> it = classes.iterator();
		while(it.hasNext()){
			Class clazz = it.next();
			classesNames = classesNames + clazz.getSimpleName() + " " +
			MiscUtility.getLowerCaseField(clazz.getSimpleName());
			if(it.hasNext()){
				classesNames += " , ";
			}
		}
		return classesNames;
	}



}
